﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Text.RegularExpressions;
using System.Text;
using System.Linq;
using System.Xml;
using System.Net;
using System.Web;
using MediaPortal.Plugins.Trailers.Common;
using LitJson;
using MediaPortal.GUI.Library;

namespace MediaPortal.Plugins.Trailers.Providers {
    public class AppleTrailers : TrailerProviderBase, IProxyHandler {
        #region Variables

        #region Apple Trailer Trailer Locations

        private const string urlBase = "http://trailers.apple.com/trailers/";

        private const string xmlNamespace = "http://www.apple.com/itms/";
        private const string urlXMLIndexAll = urlBase + "home/xml/widgets/indexall.xml";
        private const string urlXMLCurrent = urlBase + "home/xml/current.xml";
        private const string urlXMLCurrent480 = urlBase + "home/xml/current_480p.xml";
        private const string urlXMLCurrent720 = urlBase + "home/xml/current_720p.xml";
        private const string urlXMLDetails = "http://trailers.apple.com/moviesxml/s/";

        private const string urlJsonJustAdded = urlBase + "home/feeds/just_added.json";
        private const string urlJsonExlusive = urlBase + "home/feeds/exclusive.json";
        private const string urlJsonPop = urlBase + "home/feeds/most_pop.json";
        private const string urlJsonHD = urlBase + "home/feeds/just_hd.json";
        private const string urlJsonGenre = urlBase + "home/feeds/genres.json";
        private const string urlJsonStudio = urlBase + "home/feeds/studios.json";

        #endregion

        #endregion

        #region TrailerProviderBase overrides

        public override string ProviderId() {
            return "1";
        }

        public override string ProviderName() {
            return "Apple Movie Trailers";
        }

        public override string ProviderImage() {
            return "";
        }

        public override Index GetIndex() {
            Index rtIndex = new Index();
            rtIndex.Add(new IndexItem("Featured: Just Added", "/just_added", true));
            rtIndex.Add(new IndexItem("Featured: Exclusive", "/exclusive", true));
            rtIndex.Add(new IndexItem("Featured: Just HD", "/just_hd", true));
            rtIndex.Add(new IndexItem("Featured: Most Popular", "/most_pop", true));
            rtIndex.Add(new IndexItem("Featured: Genres", "/featured/genre", true));
            rtIndex.Add(new IndexItem("Featured: Movie Studio", "/featured/studio", true));
            return rtIndex;
        }

        public override Index GetIndex(string key) {
            Log.Info("[MyTrailers][Apple Trailers] Retrieving index: {0}", key);
            return fetchIndex(key);
        }

        public override Trailer GetDetails(string key) {
            Log.Info("[MyTrailers][Apple Trailers] Retrieving details: {0}", key);
            return fetchDetails(key);
        }

        #endregion

        private Index fetchIndex(string key) {
            Index items = new Index();
            string url = string.Empty;

            Log.Info("[MyTrailers][Apple Trailers] Fetching trailers for:  {0}", key);

            switch (key) {
                case "/just_added":
                    url = urlJsonJustAdded;
                    break;
                case "/exclusive":
                    url = urlJsonExlusive;
                    break;
                case "/just_hd":
                    url = urlJsonHD;
                    break;
                case "/most_pop":
                    url = urlJsonPop;
                    break;
                case "/featured/genre":
                    url = urlJsonGenre;
                    break;
                case "/featured/studio":
                    url = urlJsonStudio;
                    break;
                default:
                    if (key.StartsWith("/featured/genre")) {
                        url = urlJsonGenre;
                        break;
                    }
                    else if (key.StartsWith("/featured/studio")) {
                        url = urlJsonStudio;
                        break;
                    }
                    else {
                        return items;
                    }
            }

            List<string> trailers = getJsonTrailerIndex(url);

            if (key == "/featured/genre") {
                foreach (string trailer in trailers) {
                    Trailer t = Trailers[trailer];
                    string genre = t.Genres[0];
                    IndexItem item = new IndexItem();
                    item.Key = key + "/" + genre;
                    item.Folder = true;
                    item.Label = HttpUtility.HtmlDecode(genre);
                    if (!items.Contains(item))
                        items.Add(item);
                }
            }
            else if (key == "/featured/studio") {
                foreach (string trailer in trailers) {
                    Trailer t = Trailers[trailer];
                    string studio = t.Studio;
                    IndexItem item = new IndexItem();
                    item.Key = key + "/" + studio;
                    item.Folder = true;
                    item.Label = HttpUtility.HtmlDecode(studio);
                    if (!items.Contains(item))
                        items.Add(item);
                }
            }
            else {
                string genre = string.Empty;
                string studio = string.Empty;
                if (key.Contains("/featured/genre/"))
                    genre = key.Replace("/featured/genre/", "");

                if (key.Contains("/featured/studio/"))
                    studio = key.Replace("/featured/studio/", "");

                foreach (string trailer in trailers) {
                    Trailer t = Trailers[trailer];

                    if (!String.IsNullOrEmpty(genre) && (t.Genres[0] != genre))
                        continue;
                    if (!String.IsNullOrEmpty(studio) && (t.Studio != studio))
                        continue;

                    IndexItem item = new IndexItem();
                    item.Key = trailer;
                    item.Label = HttpUtility.HtmlDecode(t.Title);
                    item.Image = t.Thumb;

                    items.Add(item);
                }
            }

            return items;
        }

        private Trailer fetchDetails(string key) {

            Trailer trailer = Trailers.Set(key);
            string url = key.Replace("/trailers/", urlXMLDetails) + "index.xml";

            XmlNode Root = Common.Utils.GetXml(url);

            if (Root == null) {

                // the movie/trailer information url does not exist this can happen sometimes.
                // the apple site seems inconsistant with some movie urls so we are going to try
                // and look for alternatives using the other information we got from the feed.
                // Whether these are mistakes on apple side or if they do this on purpose is unclear to me

                bool failed = true;

                // store the unique alternatives url
                HashSet<string> triedUrls = new HashSet<string>();
                triedUrls.Add(url);

                // Alternative 1: Try to parse the movie foldername from the poster image filename
                if (string.IsNullOrEmpty(trailer.Thumb)) {
                    Match match = Regex.Match(trailer.Thumb, @"/([^/]+)_\d{12}.jpg$");
                    if (match.Success) {
                        string title = match.Groups[1].Value;
                        url = Regex.Replace(url, @"/([^/]+)/index.xml$", "/" + title + "/index.xml");
                        if (!triedUrls.Contains(url)) {
                            triedUrls.Add(url);
                            Root = Common.Utils.GetXml(url);
                            if (Root != null) failed = false;
                        }
                    }
                }

                // Alternative 2: Try to recreate movie foldername using the actual movie title
                if (failed && trailer.Title != null) {
                    string titleWithoutSpecialCharacters = Regex.Replace(trailer.Title, @"[\W\s]", "").ToLower();
                    string titleWithFailedEncoding = Regex.Replace(trailer.Title, @"\s", "").ToLower();
                    char[] chars = titleWithFailedEncoding.ToCharArray();

                    StringBuilder result = new StringBuilder(titleWithFailedEncoding.Length + (int)(titleWithFailedEncoding.Length * 0.1));
                    foreach (char c in chars) {
                        int value = Convert.ToInt32(c);
                        if (value < 48 || value > 127)
                            result.AppendFormat("{0:d3}", value);
                        else
                            result.Append(c);
                    }

                    titleWithFailedEncoding = result.ToString();

                    List<string> alternatives = triedUrls.ToList();
                    foreach (string alternative in alternatives) {
                        if (!failed)
                            break;

                        // title without special characters
                        url = Regex.Replace(alternative, @"/([^/]+)/index.xml$", "/" + titleWithoutSpecialCharacters + "/index.xml");
                        if (!triedUrls.Contains(url)) {
                            triedUrls.Add(url);
                            Root = Common.Utils.GetXml(url);
                            if (Root != null) failed = false;
                        }

                        if (failed) {
                            // title with failed encoding (as seen in the source sometimes)
                            url = Regex.Replace(alternative, @"/([^/]+)/index.xml$", "/" + titleWithFailedEncoding + "/index.xml");
                            if (!triedUrls.Contains(url)) {
                                triedUrls.Add(url);
                                Root = Common.Utils.GetXml(url);
                                if (Root != null) failed = false;
                            }
                        }
                    }
                }


                // Alternative 3: Try to replace the studio
                if (failed && trailer.Studio != null) {
                    string studio = Regex.Replace(trailer.Studio, @"[\W\s]", "").ToLower();

                    List<string> alternatives = triedUrls.ToList();
                    foreach (string alternative in alternatives) {
                        url = Regex.Replace(alternative, @"/s/([^/]+)/", "/s/" + studio + "/");
                        if (!triedUrls.Contains(url)) {
                            triedUrls.Add(url);
                            Root = Common.Utils.GetXml(url);
                            if (Root != null) failed = false;
                        }
                    }

                }

                // we exhausted all alternatives so we give up and return the information we do have.
                if (failed) {
                    Log.Error("[MyTrailers][Apple Trailers] No XML Found.");
                    return trailer;
                }
            }

            XmlNamespaceManager nsmgr = new XmlNamespaceManager(Root.OwnerDocument.NameTable);
            nsmgr.AddNamespace("a", xmlNamespace);

            // Now determine if this is really the correct url by comparing it to the 3rd pathelement value
            XmlNode goodUrlNode = Root.SelectSingleNode("//a:PathElement[3]", nsmgr);
            string goodUrl = "http://trailers.apple.com" + goodUrlNode.InnerText;
            if (url != goodUrl) {
                XmlNode newRoot = Common.Utils.GetXml(goodUrl);
                if (newRoot != null) Root = newRoot;
            }

            // Poster Image URL
            XmlNode Poster = Root.SelectSingleNode("//a:PictureView/@url[contains(.,'images/poster')]", nsmgr);
            if (Poster == null)
                Poster = Root.SelectSingleNode("//a:PictureView/@url[contains(.,'poster')]", nsmgr);

            if (Poster != null && Poster.Value.Length > 0) {
                // sometimes the poster urls start relative, in that case complete the address
                trailer.Poster = (Poster.Value.StartsWith("/")) ? "http://trailers.apple.com" + Poster.Value : Poster.Value;
                Log.Info("[MyTrailers][Apple Trailers] Added 1 poster.");
            }

            // Plot / Description (fuzzy)
            XmlNodeList trlPlot = Root.SelectNodes("//a:VBoxView/a:TextView/a:SetFontStyle", nsmgr);
            if (trlPlot.Count > 1) {
                trailer.Description = HttpUtility.HtmlDecode(trlPlot[2].InnerText.Trim());
            }

            // Cast
            XmlNodeList trlCast = Root.SelectNodes("//a:VBoxView/a:TextView[@styleSet='basic10']/a:SetFontStyle", nsmgr);
            foreach (XmlNode actor in trlCast) {
                string newactor = HttpUtility.HtmlDecode(actor.InnerText.Trim());
                if (!trailer.Cast.Contains(newactor))
                    trailer.Cast.Add(newactor);
            }

            // Find all the trailer pages for this movie.
            XmlNodeList videoList = Root.SelectNodes("//a:GotoURL[@target='main']", nsmgr);
            Dictionary<string, string> videoTitles = new Dictionary<string, string>();

            foreach (XmlNode trlUrl in videoList) {
                // if it does not start with a specific string
                // then ignore this url
                string videoUrl = trlUrl.Attributes["url"].Value;
                if (!videoUrl.StartsWith("/moviesxml/s/"))
                    continue;

                // if we already added this url ignore it
                if (videoTitles.ContainsKey(videoUrl))
                    continue;
                else
                    videoTitles.Add(videoUrl, null);

                Root = GetXml("http://trailers.apple.com" + videoUrl);
                if (Root == null) {
                    Log.Error("[MyTrailers][Apple Trailers] No XML Found for trailer page: {0}", videoUrl);
                    continue;
                }

                // Filter the playlist nodes to get the ones we need
                XmlNodeList playlist = Root.FirstChild.SelectNodes("//a:array/a:dict", nsmgr);

                if (playlist.Count > 0) {
                    foreach (XmlNode clip in playlist) {
                        // Get the node following the previewURL this is the video url of the trailer
                        string videourl = clip.SelectSingleNode("a:key[contains(.,'previewURL')]", nsmgr).NextSibling.InnerText;
                        // Log.Info("Found video: {0}", videourl);

                        // Filter the Ipod format we don't really need it 
                        // for display on our our TV do we?
                        if (!videourl.Contains(".m4v")) {
                            // Get the node following the songName this is the title of the trailer
                            string title = clip.SelectSingleNode("a:key[contains(.,'songName')]", nsmgr).NextSibling.InnerText;

                            // Get the node following the previewLength this is the duration of the video in seconds
                            XmlNode node = clip.SelectSingleNode("a:key[contains(.,'duration')]", nsmgr);
                            if (node == null)
                                node = clip.SelectSingleNode("a:key[contains(.,'previewLength')]", nsmgr);

                            int duration = (node != null) ? int.Parse(node.NextSibling.InnerText) : 0;

                            // get the release date of this specific trailer
                            string date = clip.SelectSingleNode("a:key[contains(.,'releaseDate')]", nsmgr).NextSibling.InnerText;

                            string label;
                            VideoQuality quality;

                            // Parse label and quality from the provided title
                            parseAppleVideoTitle(title, out label, out quality);

                            // Get existing/new video object by label
                            Video video = trailer.Media.Set(label);
                            // Add the current quality to the video
                            video.Size[quality] = new Uri(videourl);
                            // Set the duration of the video
                            video.Duration = new TimeSpan(0, 0, duration);

                            if (!String.IsNullOrEmpty(date)) {
                                DateTime dt;
                                if (DateTime.TryParse(date, out dt)) {
                                    video.Date = dt;
                                }
                            }

                        }
                    }

                }
            }
            Log.Info("[MyTrailers][Apple Trailers] Added {0} trailers.", trailer.Media.Count.ToString());
            return trailer;
        }

        private List<string> getJsonTrailerIndex(string url) {
            List<string> trailers = new List<string>();

            JsonData jsonData = GetJson(url);
            Log.Info("[MyTrailers][Apple Trailers] Found {0} items.", jsonData.Count.ToString());

            foreach (JsonData trailer in jsonData) {
                string key = (string)trailer["location"];

                // no key no movie.. nothing to see here move along
                if (string.IsNullOrEmpty(key))
                    continue;

                // If this Trailer exists just add the key to the 
                // list and don't fill the object.
                if (Trailers.ContainsKey(key)) {
                    trailers.Add(key);
                    continue;
                }

                // Little sanitiy check
                // if we do not have a title we probably don't have a movie
                string title = (string)trailer["title"];
                if (string.IsNullOrEmpty(title))
                    continue;

                // Get/Create Trailer object
                Trailer newTrailer = Trailers.Set(key);
                newTrailer.Title = HttpUtility.HtmlDecode(title);
                newTrailer.Studio = (string)trailer["studio"];
                string poster = (string)trailer["poster"];
                newTrailer.Poster = getLargePoster(poster);
                newTrailer.Thumb = poster;
                newTrailer.Rating = (string)trailer["rating"];
                try {
                    newTrailer.ReleaseDate = DateTime.Parse((string)trailer["releasedate"]);
                }
                catch { }
                // This is ugly/heavy needs better logic
                // director isn't always available
                try {
                    newTrailer.Director = HttpUtility.HtmlDecode((string)trailer["directors"]);
                }
                catch { }

                // List genres
                try {
                    foreach (JsonData genre in trailer["genre"]) {
                        newTrailer.Genres.Add(genre.ToString());
                    }
                }
                catch { }

                // This is ugly needs better logic
                // actors isn't always available
                try {
                    foreach (JsonData actor in trailer["actors"]) {
                        newTrailer.Cast.Add(HttpUtility.HtmlDecode(actor.ToString()));
                    }
                }
                catch { }

                // If we made all the way add the key to the list.
                trailers.Add(key);
            }

            // return the key list
            return trailers;
        }

        #region Apple Trailer Tricks

        private static string getLargePoster(string poster) {
            string lposter = poster.Replace("poster.jpg", "poster-xlarge.jpg");
            return lposter;
        }

        private static void parseAppleVideoTitle(string input, out string title, out VideoQuality quality) {
            // This parses a title formatted as: "Trailer Name (Quality)" 
            // from the playlist and splits it into a title and the quality enum

            string[] parts = input.Split('(');
            if (parts.Length == 2) {
                // this should be the title.
                title = parts[0].Trim();
                // this should be the quality identifier
                string q = parts[1].Replace(")", "").ToLower();
                switch (q) {
                    case "small":
                        quality = VideoQuality.SMALL;
                        break;
                    case "medium":
                        quality = VideoQuality.MEDIUM;
                        break;
                    case "large":
                        quality = VideoQuality.LARGE;
                        break;
                    case "hd 480p":
                        quality = VideoQuality.HD480;
                        break;
                    case "hd 720p":
                        quality = VideoQuality.HD720;
                        break;
                    case "hd 1080p":
                        quality = VideoQuality.FULLHD;
                        break;
                    default:
                        quality = VideoQuality.UNKNOWN;
                        break;
                }
            }
            else {
                title = input;
                quality = VideoQuality.UNKNOWN;
            }
        }

        #endregion

        #region IProxyHandler Members

        public void ModifyRequest(HttpWebRequest request) {
            request.UserAgent = "QuickTime/7.6.2";
        }        

        #endregion

    }
}
